/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
/*
 *  Copyright 2008 Global Sign In
 * 
 *  This code is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <ctype.h>
#include <stdlib.h>
#include <sstream>
#include <iostream>
#include <fstream>
#include <algorithm>

#include "Base64.h"
#include "PHPSerializer.h"
#include "MessageDetails.h"

using std::clog;
using std::endl;
using std::set;
using std::vector;
using std::string;
using std::stringstream;
using std::for_each;
using std::ifstream;
using std::ios;

// A function object to delete attachments with for_each().
struct DeleteAttachmentFunc
{
	public:
		void operator()(vector<Attachment *>::value_type &p)
		{
			delete p;
		}

};

Attachment::Attachment(const string &fileName,
	const char *pContent, unsigned long contentLen) :
	m_fileName(fileName),
	m_pEncodedContent(NULL),
	m_encodedLength(0)
{
	string fileType("text/plain");

	// What's the extension ?
	string::size_type lastPos = fileName.find_last_of('.');
	if (lastPos != string::npos)
	{
		string fileExtension(fileName.substr(lastPos + 1));

		// Guess the file type
		if (fileExtension == "pdf")
		{
			fileType = "application/pdf";
		}
		else if (fileExtension == "rtf")
		{
			fileType = "application/rtf";
		}
		else if (fileExtension == "doc")
		{
			fileType = "application/vnd.ms-word";
		}
		else if (fileExtension == "xls")
		{
			fileType = "application/vnd.ms-excel";
		}
		else if (fileExtension == "ppt")
		{
			fileType = "application/vnd.ms-powerpoint";
		}
		else if ((fileExtension == "html") ||
			(fileExtension == "htm"))
		{
			fileType = "text/html";
		}
	}
				
	m_headers = "Content-Type: ";
	m_headers += fileType;
	m_headers += ";\r\n\tname=\"";
	m_headers += fileName;
	m_headers += "\"\r\nContent-Transfer-Encoding: base64\r\nContent-Disposition: attachment;\r\n\tfilename=\"";
	m_headers += fileName;
	m_headers += "\"\r\n\r\n";

	// Encode the content
	unsigned long dataLen = contentLen;
	m_pEncodedContent = Base64::encode(pContent, dataLen);
	if (m_pEncodedContent != NULL)
	{
		m_encodedLength = dataLen;
	}
	else
	{
		clog << "Couldn't encode contents of " << fileName << endl;
	}
}

Attachment::~Attachment()
{
	if (m_pEncodedContent != NULL)
	{
		delete[] m_pEncodedContent;
		m_pEncodedContent = NULL;
	}
}

MessageDetails::MessageDetails() :
	m_personalizeSubject(false),
	m_personalizePlain(false),
	m_personalizeHtml(false),
	m_pSubjectSub(NULL),
	m_pPlainSub(NULL),
	m_pHtmlSub(NULL),
	m_isRecipientPersonalized(false)
{
	// These are the fields to substitute
	m_fieldNames.insert("{Name}");
	m_fieldNames.insert("{emailaddress}");
	m_fieldNames.insert("{recipientId}");
	m_fieldNames.insert("{customfield1}");
	m_fieldNames.insert("{customfield2}");
	m_fieldNames.insert("{customfield3}");
	m_fieldNames.insert("{customfield4}");
	m_fieldNames.insert("{customfield5}");
	m_fieldNames.insert("{customfield6}");
}

MessageDetails::~MessageDetails()
{
	if (m_pSubjectSub != NULL)
	{
		delete m_pSubjectSub;
	}
	if (m_pPlainSub != NULL)
	{
		delete m_pPlainSub;
	}
	if (m_pHtmlSub != NULL)
	{
		delete m_pHtmlSub;
	}

	for_each(m_attachments.begin(), m_attachments.end(),
		DeleteAttachmentFunc());
}

char *MessageDetails::loadRaw(const string &filePath, int &fileSize)
{
	ifstream dataFile;

	dataFile.open(filePath.c_str());
	if (dataFile.is_open() == true)
	{
		dataFile.seekg(0, ios::end);
		int length = dataFile.tellg();
		dataFile.seekg(0, ios::beg);

		char *pBuffer = new char[length + 1];
		dataFile.read(pBuffer, length);
		if (dataFile.fail() == false)
		{
			pBuffer[length] = '\0';
			dataFile.close();
			fileSize = length;

			return pBuffer;
		}

		dataFile.close();
	}

	return NULL;
}

string MessageDetails::encodeRecipientId(const string &recipientId)
{
	string idStr("&recipientId=");
	idStr += recipientId;
	idStr += "&";

	unsigned long dataLen = idStr.size();
	char *pData = Base64::encode(idStr.c_str(), dataLen);

	if ((pData == NULL) ||
		(dataLen == 0))
	{
		return "";
	}

	string encodedData(pData, dataLen);

	delete[] pData;

	return encodedData;
}

string MessageDetails::createMessageId(const string &suffix)
{
	stringstream randomStr;
	string messageId;

	randomStr << lrand48() << m_fromEmailAddress << lrand48();

	string pseudoName(randomStr.str());
	unsigned long msgIdLen = (unsigned long)pseudoName.length();
	char *pMessageId = Base64::encode(pseudoName.c_str(), msgIdLen);
	if (pMessageId != NULL)
	{
		messageId = pMessageId;
		delete[] pMessageId;

		// Remove non-alphanumerics
		string::size_type idLen = messageId.length();
		for (string::size_type pos = 0; pos < idLen; ++pos)
		{
			if (isalnum(messageId[pos]) == 0)
			{
				messageId.erase(pos, 1);
			}
		}
		if (idLen > 40)
		{
			messageId.resize(40);
		}
	}
	messageId += suffix;

	return messageId;
}

Substitute *MessageDetails::getSubjectSubstituteObj(bool hasFields)
{
	if (m_pSubjectSub == NULL)
	{
		m_pSubjectSub = newSubstitute(m_subject, hasFields,
			false, false);
	}

	return m_pSubjectSub;
}

Substitute *MessageDetails::getPlainSubstituteObj(bool hasFields)
{
	if (m_pPlainSub == NULL)
	{
		m_pPlainSub = newSubstitute(m_plainContent, hasFields,
			false, true);
	}

	return m_pPlainSub;
}

Substitute *MessageDetails::getHtmlSubstituteObj(bool hasFields)
{
	if (m_pHtmlSub == NULL)
	{
		m_pHtmlSub = newSubstitute(m_htmlContent, hasFields,
			true, true);
	}

	return m_pHtmlSub;
}

bool MessageDetails::isRecipientPersonalized(void) const
{
	return m_isRecipientPersonalized;
}

void MessageDetails::setAttachments(const vector<string> &filePaths)
{
	// Delete and clear all attachments, if any
	for_each(m_attachments.begin(), m_attachments.end(),
		DeleteAttachmentFunc());
	m_attachments.clear();

	for (vector<string>::const_iterator fileIter = filePaths.begin();
		fileIter != filePaths.end(); ++fileIter)
	{
		string fileName("Attachment");
		int contentLen = 0;

		// Load the file
		char *pContent = loadRaw(*fileIter, contentLen);
		if (pContent == NULL)
		{
			clog << "Couldn't load contents of " << *fileIter << endl;
			continue;
		}

		// Get the actual file name
		string::size_type lastPos = fileIter->find_last_of("/");
		if (lastPos != string::npos)
		{
			fileName = fileIter->substr(lastPos + 1);
		}
#ifdef DEBUG
		clog << "MessageDetails::setAttachments: attaching " << fileName << endl;
#endif

		m_attachments.push_back(new Attachment(fileName, pContent, contentLen));

		delete[] pContent;
	}
}

void MessageDetails::setAttachmentArray(const string &serializedArray)
{
	vector<string> filesToAttach;

	m_attachmentArray = serializedArray;
	if (PHPSerializer::unserializeArrayOfStrings(m_attachmentArray, filesToAttach) == false)
	{
		filesToAttach.clear();
	}

	setAttachments(filesToAttach);
}

string MessageDetails::getAttachmentArray(void) const
{
	return m_attachmentArray;
}

unsigned int MessageDetails::getAttachmentCount(void) const
{
	return m_attachments.size();
}

string MessageDetails::getAttachmentHeaders(unsigned int attachmentNum)
{
	vector<Attachment *>::iterator attachmentIter = m_attachments.begin();
	unsigned int pos = 0;

	while (attachmentIter != m_attachments.end())
	{
		if (pos == attachmentNum)
		{
			return (*attachmentIter)->m_headers;
		}

		// Next
		++attachmentIter;
		++pos;
	}

	return "";
}

const char *MessageDetails::getEncodedAttachment(unsigned int attachmentNum, unsigned long &dataLen) const
{
	vector<Attachment *>::const_iterator attachmentIter = m_attachments.begin();
	unsigned int pos = 0;

	while (attachmentIter != m_attachments.end())
	{
		if (pos == attachmentNum)
		{
			dataLen = (*attachmentIter)->m_encodedLength;

			return (*attachmentIter)->m_pEncodedContent;
		}

		// Next
		++attachmentIter;
		++pos;
	}

	return NULL;
}

Substitute *MessageDetails::newSubstitute(const string &contentTemplate, bool hasFields,
	bool escapeEntities, bool encodeQP)
{
	Substitute *pSub = new Substitute(contentTemplate, hasFields,
			m_fieldNames, escapeEntities, encodeQP);

	// Does it have any recipient-specific field ?
	if ((pSub->hasField("{Name}") == true) ||
		(pSub->hasField("{emailaddress}") == true) ||
		(pSub->hasField("{recipientId}") == true))
	{
		m_isRecipientPersonalized = true;
	}

	return pSub;
}
